using System;
using System.Collections.Generic;
using System.Text;
using XMLStuffs;

namespace Chanutils
{

    public enum BandwidthLevel
    {
        Low,
        Medium,
        High,
        Insane
    }

    // This basically exclusively handles the protocol part of things.
    //
    public class P2PHigh
    {
        // lol conneciozoni
        P2PMan PeerManager;
        Peer[] PeerInfo;

        BandwidthLevel Bandwidthness;

        public P2PHigh(int NumberOfPeers, int ListenPort, BandwidthLevel OurBandwidth, string DirectoryServerIP, int DirectoryServerPort)
        {
            // Stuff
            Bandwidthness = OurBandwidth;

            // Get our super peer dataz array ready..
            PeerInfo = new Peer[NumberOfPeers];

            // Initialise low level per-connection manager...
            PeerManager = new P2PMan();
            //PeerManager = new P2PMan("0.0.0.0", ListenPort, DirectoryServerIP, DirectoryServerPort, NumberOfPeers);

            // Now, hook our shit in!
            PeerManager.OnPeerCheckNeeded += new IsPeerOK(AcknowledgePeer);
            PeerManager.OnUnwantedPeer += new GetReturnMsgForUnwantedPeer(GotUnwantedPeer);
            PeerManager.OnPeerDisconnect += new PeerDeath(RegisterPeerDeath);
            PeerManager.OnPeerSentData += new PeerReceivedData(RegisterPeerDataReceived);
            PeerManager.OnNewPeerConnected += new PeerNew(RegisterNewPeer);
            PeerManager.OnWantedPeer += new GetReturnMsgForNewPeer(GotWantedPeer);

            // ... and finish the connection manager initialisation, and start getting peers automatically ^^ ...
            PeerManager.StartP2P("0.0.0.0", ListenPort, DirectoryServerIP, DirectoryServerPort, NumberOfPeers);
        }

        internal void RegisterNewPeer(int PeerNum, string PeerIP, int PeerPort)
        {
            System.Console.WriteLine("[P2P] Registered new peer connection [" + PeerNum.ToString() + "] " + PeerIP + ":" + PeerPort.ToString());
            PeerInfo[PeerNum] = new Peer(PeerIP, PeerPort);
        }

        internal void RegisterPeerDeath(int PeerNum)
        {
            System.Console.WriteLine("[P2P] Registered peer death [" + PeerNum.ToString() + "]");
            PeerInfo[PeerNum] = null;
        }

        internal Peer PeerDataRequest(int PeerNum)
        {
            return PeerInfo[PeerNum];
        }

        internal void RegisterPeerDataReceived(int PeerNum, string MessageHeader, byte[] MessageAdditional)
        {

        }

        internal byte[] GotUnwantedPeer()
        {
            // Oh joy.
            return Protocol.MsgNoGoAway();
        }

        internal byte[] GotWantedPeer()
        {
            // Yay!  Let's return our capabilities, liek, right now.
            return Protocol.MsgPeerIdentify(Bandwidthness, new string[] { "BETA", "CHAN" });
        }

        internal bool AcknowledgePeer(string IP, int Port)
        {
            // TODO: Allow IP bans etc. with this
            // .... and until then ....
            System.Console.WriteLine("[P2P] Acknowledging (returning true, TODO ip check and stufflol) connection request from " + IP + ":" + Port.ToString());
            return true;
        }
    }

    internal class ProtocolReader
    {

    }

    public static class Protocol
    {
        private static byte[] FormatMessage(string MessageHeader, byte[] MessageBin)
        {
            // If we actually HAVE a MessageBin...
            if (MessageBin != null)
            {
                
                byte[] xTmpBinary = HELP.ZlibEncode(MessageBin);
                byte[] xTmpHeader = System.Text.Encoding.ASCII.GetBytes(MessageHeader + " " + xTmpBinary.Length.ToString() + "\r\n");
                byte[] xFinal = new byte[xTmpHeader.Length + xTmpBinary.Length];

                // Copy in header...
                Array.Copy(xTmpHeader, 0, xFinal, 0, xTmpHeader.Length);
                // Copy binary...
                Array.Copy(xTmpBinary, 0, xFinal, xTmpHeader.Length, xTmpBinary.Length);
                // And present! ^^

                return xFinal;
            }
            else
            {
                return System.Text.Encoding.ASCII.GetBytes(MessageHeader + " 0\r\n");
            }
        }

        private static byte[] FormatMessage(string MessageHeader, byte[] MessageBin, bool NeverHasMessageBin)
        {
            if (NeverHasMessageBin)
                return System.Text.Encoding.ASCII.GetBytes(MessageHeader + "\r\n");
            else
                return FormatMessage(MessageHeader, MessageBin);
        }

        /// <summary>
        /// Formats a message header, from the parameters.  This should be fed to FormatMessage() afterwards.
        /// </summary>
        /// <param name="Bits">Message header parts; DO NOT INCLUDE '{length of attached new thread xml}', FormatMessage() WILL HANDLE THIS FOR YOU</param>
        /// <param name="HopCountIfAny">Initial 'hop remaining' count, if any? (0 to not include parameter)</param>
        /// <returns>String that should be fed to FormatMessage().</returns>
        private static string FormatMessageHeader(string[] Bits, int HopCountIfAny)
        {
            string sFinal = "";

            // TODO: Performance consideration, learn how to use StringBuilder thingy???
            //

            foreach (string s in Bits)
                sFinal += s + " ";

            if (HopCountIfAny > 0)
                // .. Yup
                sFinal += HopCountIfAny.ToString();
            else
                // Meh.  Remove that last space, then...
                sFinal = sFinal.Substring(0, sFinal.Length - 1);


            // Return result!! ^^
            return sFinal;
        }

        /// <summary>
        /// Concatenate and spaces an array of strings.
        /// </summary>
        /// <param name="Strings">The strings to be concatenated and spaced</param>
        /// <returns>Returns a concatenated and spaced version of the initial strings.</returns>
        private static string ConcatenateAndSpace(string[] Strings)
        {
            string sFinal = "";

            if (Strings.Length > 0)
            {
                foreach (string s in Strings)
                    sFinal += s + " ";

                sFinal = sFinal.Substring(0, sFinal.Length - 1);
            }

            return sFinal;
        }


        // Aand the real stuff we're going to expose. ^^
        //
        public static byte[] MsgClose(string Reason)
        {
            return FormatMessage(FormatMessageHeader(new string[] { "CLOSE", Reason }, 0), null, true);
        }
        
        public static byte[] MsgNewThreadNotify(long UniqueId, string BoardId, Thread NewThread)
        {
        	return FormatMessage(FormatMessageHeader(new string[] { "NEWTHREADNOTIFY", UniqueId.ToString(), MakeBoardIDSafe(BoardId), NewThread.ID.ToString() }, 0), XmlParser.ThreadToXml(NewThread));
        }

        public static byte[] MsgNoGoAway()
        {
            return FormatMessage(FormatMessageHeader(new string[] { "NOGOAWAY" }, 0), null, true);
        }

        public static byte[] MsgBinaryNotify(int UniqueId, long BinaryId, long FileSizeInKilobytes)
        {
            return FormatMessage(FormatMessageHeader(new string[] { "BINARYNOTIFY", UniqueId.ToString(), BinaryId.ToString(), FileSizeInKilobytes.ToString() }, 0), null, true);
        }

        public static byte[] MsgPeerIdentify(BandwidthLevel TheBandwidthLevel, string[] CapabilitiesList)
        {
            return FormatMessage(FormatMessageHeader(new string[] { "PEERIDENTIFY", ((int)TheBandwidthLevel).ToString() }, 0), System.Text.Encoding.ASCII.GetBytes(ConcatenateAndSpace(CapabilitiesList)));
        }

        public static byte[] MsgThreadNotify(int UniqueId, string BoardID, int ThreadID, Post NewPost)
        {
            return FormatMessage(FormatMessageHeader(new string[] { "THREADNOTIFY", MakeBoardIDSafe(BoardID), ThreadID.ToString() }, 0), XmlParser.PostToXml(NewPost));
        }

        // 'Request' version
        public static byte[] MsgGetBinaryDetail(int UniqueId, long BinaryID, int HopsRemaining)
        {
            return FormatMessage(FormatMessageHeader(new string[] { "GETBINARYDETAIL", UniqueId.ToString(), BinaryID.ToString() }, HopsRemaining), null);
        }

        // 'Response' version
        public static byte[] MsgGetBinaryDetail(int UniqueId, long BinaryID, int HopsRemaining, string HashOfWholeBinary, long BinaryLengthInBytes)
        {
            return FormatMessage(FormatMessageHeader(new string[] { "GETBINARYDETAIL", UniqueId.ToString(), BinaryID.ToString() }, HopsRemaining), System.Text.Encoding.ASCII.GetBytes(HashOfWholeBinary + "\r\n" + BinaryLengthInBytes.ToString() + "\r\n"));
        }

        public static string MakeBoardIDSafe(string BoardID)
        {
            return BoardID.Replace(' ', '_');
        }

        // 'Request' version
        //public static byte[] MsgList2(int UniqueId, string BoardId, 


    }

    public class Peer
    {
        /*
        private System.IO.MemoryStream InputBuffer;
         */
        
        // lol, US  D=
        public string PeerIP;
        public int PeerPort;

        public int AbuseCount;

        public BandwidthLevel BandwidthCapabilities;


        /*
        #region Thread-safing measures...
            private bool ThreadLocked;

            /// <summary>
            /// Thread-safing measure; makes sure InputBuffer is safe to access.  
            /// DON'T FORGET TO CALL UnlockStream() AFTERWARDS
            /// </summary>
            private void LockStream()
            {
                System.DateTime dtComplainTime = System.DateTime.Now.AddSeconds(20);

                do
                {
                    // Give up and complain?
                    if (System.DateTime.Now.CompareTo(dtComplainTime) < 0)
                        throw new Exception("'Peer' class stream has been locked for over 20 seconds!  Bailing out.");
                    
                    // Otherwise... sleep an ms or two :p
                    System.Threading.Thread.Sleep(1);
                }
                while (ThreadLocked);

                // Excellent!  Now then, let's lock this thing for ourselves. ^^
                ThreadLocked = true;
            }

            /// <summary>
            /// Call this when you're done with InputBuffer!!  YOU MUST. Or everybody dies
            /// </summary>
            private void UnlockStream()
            {
                ThreadLocked = false;
            }
        #endregion
        */

        public Peer(string IP, int Port)
        {
            PeerIP = IP;
            PeerPort = Port;
            //InputBuffer = new System.IO.MemoryStream();
            AbuseCount = 0;

            // just in case?
            //UnlockStream();
        }

        /*
        /// <summary>
        /// Appends the provided byte[] array to the internal buffer.
        /// </summary>
        /// <param name="NewData">Byte[] array to be appended to the internal buffer...</param>
        public void AppendToBuffer(byte[] NewData)
        {
            // Make sure we're safe...
            LockStream();

            // Ok, write!! ^^
            InputBuffer.Write(NewData, 0, NewData.Length);
            
            // ...Aaand unlock.
            UnlockStream();
        }

        public byte[] GetBuffer()
        {
            byte[] xOutBuffer;

            // Make sure we're safe...
            LockStream();

            // Get...
            xOutBuffer = new byte[InputBuffer.Length];
            InputBuffer.Read(xOutBuffer, 0, xOutBuffer.Length);

            // Return!
            UnlockStream();

            return xOutBuffer;
        }

        public void ReplaceBuffer(byte[] Data)
        {
            // Make sure we're safe..
            LockStream();

            InputBuffer = new System.IO.MemoryStream(Data);

            UnlockStream();
        }
         */
    }
}
